package com.tm1cobi.sendmail.sender;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.event.TransportEvent;
import javax.mail.event.TransportListener;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class MailSender {
    final static Logger logger = LoggerFactory.getLogger(MailSender.class);

    private String host;
    private String port;
    private String username;
    private String password;

    private String addresses;
    private String title;
    private String contentPath;

    private Address[] invalidAddresses;
    private Address[] validAddresses;

    //
    public MailSender(final String host, final String port, final String username,
            final String password, final String addresses, final String title,
            final String contentPath, Address[] invalidAddresses) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;

        this.addresses = addresses;
        this.title = title;
        this.contentPath = contentPath;

        this.validAddresses = null;
        this.invalidAddresses = invalidAddresses;
    }

    public MailSender(final String host, final String port, final String username,
            final String password, final String addresses, final String title,
            final String contentPath) {
        this(host, port, username, password, addresses, title, contentPath, null);
    }


    public void send() {
        try {
            Properties props = new Properties();
            props.put("mail.smtp.host", host);
            props.put("mail.smtp.port", String.valueOf(port));
            props.put("mail.smtp.auth", "true");

            Transport transport = null;
            Session session = Session.getDefaultInstance(props, null);
            transport = session.getTransport("smtp");
            //
            final DeliveredStateFuture future = new DeliveredStateFuture();
            transport.addTransportListener(new TransportListener() {
                @Override
                public void messageDelivered(TransportEvent arg0) {
                    System.out.println("[SUCCESS] ");
                    writeLog();
                    future.setState(DeliveredState.MESSAGE_DELIVERED);
                    //
                }

                @Override
                public void messageNotDelivered(TransportEvent arg0) {
                    System.out.println("[Failure] messageNotDelivered" + arg0.getMessage());
                    logger.error("[Failure] ");
                    future.setState(DeliveredState.MESSAGE_NOT_DELIVERED);
                }

                @Override
                public void messagePartiallyDelivered(TransportEvent arg0) {
                    System.out.println("[Failure] messagePartiallyDelivered" + arg0.getMessage());
                    System.out.println("[Failure] ");
                    logger.error("[Failure] ");
                    future.setState(DeliveredState.MESSAGE_PARTIALLY_DELIVERED);
                }
            });
            //
            transport.connect(host, username, password);
            MimeMessage msg = new MimeMessage(session);
            msg.setSentDate(new Date());
            InternetAddress fromAddress = new InternetAddress(username, username, "UTF-8");
            msg.setFrom(fromAddress);
            //
            String[] addressArr = addresses.split(";");
            validAddresses = new InternetAddress[addressArr.length];
            for (int i = 0; i < addressArr.length; i++) {
                validAddresses[i] = new InternetAddress(addressArr[i]);
            }
            msg.setRecipients(Message.RecipientType.TO, validAddresses);
            msg.setSubject(title, "UTF-8");
            //
            Path configFile = Paths.get(contentPath);

            BufferedReader reader = Files.newBufferedReader(configFile, StandardCharsets.UTF_8);
            StringBuffer sb = new StringBuffer();
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            //
            msg.setContent(sb.toString(), "text/html;charset=UTF8");
            msg.saveChanges();
            transport.sendMessage(msg, msg.getAllRecipients());
            transport.close();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        } catch (MessagingException e) {
            if (e instanceof SendFailedException) {
                SendFailedException ex = (SendFailedException) e;
                invalidAddresses = ex.getInvalidAddresses();
                //
                List<Address> allAddressList = new ArrayList<Address>();
                Collections.addAll(allAddressList, validAddresses);
                //
                List<Address> invalidAddressList = new ArrayList<Address>();
                Collections.addAll(invalidAddressList, invalidAddresses);
                // 求差集
                allAddressList.removeAll(invalidAddressList);
                List<Address> validAddressList = allAddressList;
                validAddresses = validAddressList.toArray(new Address[validAddressList.size()]);
                //
                if (validAddresses != null && validAddresses.length > 0) {
                    new MailSender(host, port, username, password, StringUtils.join(validAddresses,
                            ";"), title, contentPath, invalidAddresses).send();
                } else {
                    // 全部失败
                    writeLog();
                }
            } else {
                e.printStackTrace();
                logger.error(e.getMessage());
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        }
    }

    private void writeLog() {
        Path logFile = FileSystems.getDefault().getPath("E:/BMW_TM1/SendEmail/SmtpLogsOutput.csv");
        //
        boolean logFileExists = Files.exists(logFile, new LinkOption[] {LinkOption.NOFOLLOW_LINKS});
        if (logFileExists) {
            try {
                boolean success = Files.deleteIfExists(logFile);
                System.out.println("Delete status: " + success);
            } catch (IOException | SecurityException e) {
                System.err.println(e);
            }
        } else {
            Set<PosixFilePermission> perms1 = PosixFilePermissions.fromString("rwxrwxrwx");
            FileAttribute<Set<PosixFilePermission>> attr2 =
                    PosixFilePermissions.asFileAttribute(perms1);
            try {
                Files.createFile(logFile, attr2);
            } catch (IOException e) {
                System.err.println(e);
            }
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        Calendar c1 = Calendar.getInstance();
        c1.setTime(new Date());
        String dateStr = format.format(c1.getTime());
        //
        String[] csvArr = null;
        //
        if (validAddresses != null && validAddresses.length > 0) {
            String res = "Succ";
            if (invalidAddresses != null && invalidAddresses.length > 0) {
                res = "Failure";
            }
            csvArr =
                    new String[] {dateStr, getAddressesStr(validAddresses), title, res,
                            getAddressesStr(invalidAddresses)};
        } else {
            csvArr =
                    new String[] {dateStr, "", title, "Failure", getAddressesStr(invalidAddresses)};
        }
        //
        //
        String content = StringUtils.join(csvArr, ",");
        try {
            byte[] contentbyte = content.getBytes("UTF-8");
            Files.write(logFile, contentbyte);
        } catch (IOException e) {
            System.err.println(e);
        }
    }

    private String getAddressesStr(Address[] addresses) {
        if (addresses != null && addresses.length > 0) {
            String[] stres = new String[addresses.length];
            for (int i = 0; i < addresses.length; i++) {
                stres[i] = addresses[i].toString();
            }
            return StringUtils.join(stres, ";");
        } else {
            return "";
        }
    }

    //
    public String getAddresses() {
        return addresses;
    }

    public void setAddresses(String addresses) {
        this.addresses = addresses;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContentPath() {
        return contentPath;
    }

    public void setContentPath(String contentPath) {
        this.contentPath = contentPath;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }


    private enum DeliveredState {
        INITIAL, MESSAGE_DELIVERED, MESSAGE_NOT_DELIVERED, MESSAGE_PARTIALLY_DELIVERED,
    }
    private static class DeliveredStateFuture {
        private DeliveredState state = DeliveredState.INITIAL;

        synchronized void waitForReady() throws InterruptedException {
            if (state == DeliveredState.INITIAL) {
                wait();
            }
        }

        synchronized DeliveredState getState() {
            return state;
        }

        synchronized void setState(DeliveredState newState) {
            state = newState;
            notifyAll();
        }
    }

}
